JavaScript Event Loop
一、Event Loop
1、过程描述
首先 JavaScript 是单线程的脚本语言,因为假定 JS 同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器就不知道以哪个线程为准。
所以 JavaScript 的运行机制是事件循环(Event Loop),即调用栈【3】不断的向宏任务队列【4】里读取事件。
当执行 JS 脚本的时候,浏览器会把 JS 脚本当成一个宏任务【1】来运行,在执行过程中会产生执行环境,这些执行环境会被顺序的加入到调用栈中。
当执行遇到 setTimeout 之类的宏任务,就把他们推入宏任务队列,在下一轮宏任务执行时调用。
- 当执行遇到 Promise 之类的微任务【2】,就把他们推入到当前宏任务的微任务队列中,在本轮宏任务的同步代码都执行完成后,依次执行所有的微任务。
- 当调用栈为空时,调用栈会读取宏任务队列中的任务去执行。
2、概念
【1】宏任务
在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,所以宿主环境传递给 JavaScript 引擎一段代码,引擎就把代码直接顺次执行了,这个任务是宿主发起的任务,叫做宏任务。【包括 script、setTimeout、setInterval、MessageChannel、postMessage、setImmediate】
【2】微任务
在 ES5 之后,JavaScript 引入了 Promise,这样不需要浏览器的安排,JavaScript引擎本身也可以发起任务了。 这个任务叫做微观任务。【包括 Promise、process.nextTick、MutationObsever】
【3】调用栈
是一个记录当前程序所在位置的数据结构,如果当前进入了某个函数,这个函数会被放在栈里面,如果当前离开某个函数,这个函数就会被弹出栈外。 即利用这个栈的特殊结构实现同步。js的运行环境有且只有一个调用栈。
【4】宏任务队列
宏任务队列是一个先进先出的数据结构,排在前面的事件,优先被调用栈读取。
3、示例
示例一
1 | console.log('script start') |
示例二
1 | console.log('script start') |
示例三
1 | console.log('1') |
⚠️ Promise 中的异步体现在 then 和 catch 中,所以写在Promise中的代码是被当做同步任务立即执行的。
二、Event Loop 进阶
1、过程描述
写在 Promise 中的代码会被当做同步任务立即执行, Promise 中的异步体现在 then 和 catch 中。
出现在 await 之前的代码也是立即执行的,之后的代码异步执行。
很多人以为 await 会一直等待之后的表达式执行完之后才继续执行后面的代码,实际上 await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将 await 后面的代码加入到微任务队列中,然后跳出整个 async 函数来执行后面的代码。
2、示例
示例一
1 | async function async1() { |